#include "../include/lcdtext.h"

unsigned char font_height;
unsigned char font_width; 
unsigned char *font;

unsigned char lcd_setfont(unsigned char* newfont, unsigned char height, unsigned char width)
{
    if((height != LCD_FONT_8PIX) && (height != LCD_FONT_16PIX))
        return ERR;
    if(width > 61)
        return ERR;
    font_height = height;
    font_width = width;
    font = newfont;
    return OK;
}

unsigned char lcd_gettextwidth()
{
    return LINE_WIDTH;
}

unsigned char lcd_gettextheight()
{
    return (font_height == LCD_FONT_8PIX)?4:2;
}

unsigned char lcd_writechar(unsigned char pos, unsigned char character)
{
    unsigned char line = pos/LINE_WIDTH;
    unsigned char lpos = pos%LINE_WIDTH;
    unsigned char side = (lpos*font_width) >= (LCD_WIDTH/2);
    unsigned char j, d;

    if(font_height == LCD_FONT_8PIX)
    {
        if (line > 3)
            return ERR;
        lcd_setpage(line);
        lcd_setcol(side, lpos*font_width - side*LCD_WIDTH/2);
        for(j=0; j<font_width; j++)
        {
            if( (lpos*font_width + j - (LCD_WIDTH/2))%LCD_WIDTH == 0)
            {
                side = 1;
                lcd_setcol(side, 0);
            }
            if((character >= ASCII_OFFSET) && (character <= ASCII_MAX)) // an alphanumeric symbol
                lcd_writebyte(side, font[(character - ASCII_OFFSET)*font_width + j]);
            else // else, substitute with a whitespace
                lcd_writebyte(side, font[(' ' - ASCII_OFFSET)*font_width + j]);
        }
        return OK;
    }
    else if(font_height == LCD_FONT_16PIX)
    {
        if (line > 1)
            return ERR;
        for(d=0; d<=1; d++)
        {
            lcd_setpage(line*2 + d);
            lcd_setcol(side, lpos*font_width - side*LCD_WIDTH/2);
            for(j=0; j<font_width; j++)
            {
                if( (lpos*font_width + j - (LCD_WIDTH/2))%LCD_WIDTH == 0)
                {
                    side = 1;
                    lcd_setcol(side, 0);
                }
                if((character >= ASCII_OFFSET) && (character <= ASCII_MAX)) // an alphanumeric symbol
                    lcd_writebyte(side, font[(character - ASCII_OFFSET)*font_width*2 + d*font_width + j]);
                else // else, substitute with a whitespace
                    lcd_writebyte(side, font[(character - ASCII_OFFSET)*font_width*2 + d*font_width + j]);
            }
        }
        return OK;
    }
    else
        return ERR;
}

unsigned char lcd_writetext(unsigned char pos, unsigned char len, unsigned char* buf)
{
    unsigned char line;
    unsigned char lpos;
    unsigned char nlines;
    unsigned char i, j;
    unsigned char side;
    unsigned char d = 0;

    if(font_height == LCD_FONT_8PIX)
    {
        line = pos/LINE_WIDTH;
        lpos = pos%LINE_WIDTH;
        nlines = (lpos+len)/LINE_WIDTH;
        side = (lpos*font_width) >= (LCD_WIDTH/2); // 0 - left, 1 - right
        if (line + nlines-1 > 3)
            return ERR;
        lcd_setcol(side, lpos*font_width - side*LCD_WIDTH/2);
        lcd_setpage(line);
        for(i=0; i<len; i++)
        {
            if( ( ((lpos+i)%LINE_WIDTH) == 0 ) && (lpos + i != 0))
            {
                side = 0;
                line++;
                lcd_setcol(side, 0);
                lcd_setpage(line);
            }
            for(j=0; j<font_width; j++) // write symbol
            {
                if( ((lpos+i)%LINE_WIDTH)*font_width + j == LCD_WIDTH/2 )
                {
                    side = 1;
                    lcd_setcol(side, 0);
                }
                if((buf[i] >= ASCII_OFFSET) && (buf[i] <= ASCII_MAX))
                    lcd_writebyte(side, font[(buf[i] - ASCII_OFFSET)*font_width + j]);
                else
                    lcd_writebyte(side, font[(' ' - ASCII_OFFSET)*font_width + j]);
            }
        }
        return OK;
    }
    else if(font_height == LCD_FONT_16PIX)
    {
        for(d=0; d<2; d++)
        {
            line = pos/LINE_WIDTH;
            lpos = pos%LINE_WIDTH;
            nlines = (lpos+len)/LINE_WIDTH;
            if (line + nlines-1> 1)
                return ERR;
            side = (lpos*font_width) >= (LCD_WIDTH/2); // 0 - left, 1 - right
            lcd_setcol(side, lpos*font_width - side*LCD_WIDTH/2);
            lcd_setpage(line*2 + d);
            for(i=0; i<len; i++)
            {
                if( ( ((lpos+i)%LINE_WIDTH) == 0 ) && (lpos + i != 0))
                {
                    side = 0;
                    line++;
                    lcd_setcol(side, 0);
                    lcd_setpage(line*2 + d);
                }
                for(j=0; j<font_width; j++) // write half of a symbol
                {
                    if( ((lpos+i)%LINE_WIDTH)*font_width + j == LCD_WIDTH/2 )
                    {
                        side = 1;
                        lcd_setcol(side, 0);
                    }
                    if((buf[i] >= ASCII_OFFSET) && (buf[i] <= ASCII_MAX))
                        lcd_writebyte(side, font[(buf[i] - ASCII_OFFSET)*font_width*2 + d*font_width + j]);
                    else
                        lcd_writebyte(side, font[(' ' - ASCII_OFFSET)*font_width*2 + d*font_width + j]);
                }
        }
        }
        return OK;
    }
    else
        return ERR;

}

unsigned char lcd_status(unsigned char con)
{
    unsigned char* address;
    if(con > 1)
        return 0xFF;
    if(con == 0)
        address = (unsigned char *)(IND1_BASE_ADDRESS | ADDR_CMD);
    else
        address = (unsigned char *)(IND2_BASE_ADDRESS | ADDR_CMD);
    return *address;
}

static void lcd_writecmd(unsigned char cmd, unsigned char arg)
{
    unsigned char* address;
    
    address = (unsigned char *)(IND1_BASE_ADDRESS | ADDR_CMD);
    while(lcd_status(0) & 0x90);
    *address = cmd|arg;
    address = (unsigned char *)(IND2_BASE_ADDRESS | ADDR_CMD);
    while(lcd_status(1) & 0x90);
    *address = cmd|arg;
}

inline void lcd_onoff(unsigned char onoff)
{

    if(onoff > 1)
        return;
    lcd_writecmd(0xAE, onoff);
}


inline void lcd_adc(unsigned char inv)
{
    if(inv > 1)
        return;
    lcd_writecmd(0xA0, inv);
}

inline void lcd_stdrive(unsigned char onoff)
{
    if(onoff > 1)
        return;
    lcd_writecmd(0xA4, onoff);
}

inline void lcd_duty(unsigned char duty)
{
    if(duty > 1)
        return;
    lcd_writecmd(0xA8, duty);
}

inline void lcd_setpage(unsigned char page)
{
    if(page > 3)
        return;
    lcd_writecmd(0xB8, page);
}

inline void lcd_setstring(unsigned char n)
{
    if(n > 31)
        return;
    lcd_writecmd(0xC0, n);
}

void lcd_setcol(unsigned char side, unsigned char col)
{
    unsigned char* address;
    if( (side>1) || (col>LCD_WIDTH/2) )
        return;
    while(lcd_status(side) & 0x90);
    if ( side == 0 )
        address = (unsigned char *)(IND1_BASE_ADDRESS | ADDR_CMD);
    else
        address = (unsigned char *)(IND2_BASE_ADDRESS | ADDR_CMD);
    *address = col;
}

void lcd_writebyte(unsigned char side, unsigned char byte)
{
    unsigned char* address;
    if( side > 1 )
        return;
    while(lcd_status(side) & 0x90);
    if ( side == 0 )
        address = (unsigned char *)(IND1_BASE_ADDRESS | ADDR_DATA);
    else
        address = (unsigned char *)(IND2_BASE_ADDRESS | ADDR_DATA);
    *address = byte;
}

unsigned char lcd_readbyte(unsigned char side)
{
    unsigned char* address;
    if( side > 1 )
        return 0;
    while(lcd_status(side) & 0x90);
    if ( side == 0 )
        address = (unsigned char *)(IND1_BASE_ADDRESS | ADDR_DATA);
    else
        address = (unsigned char *)(IND2_BASE_ADDRESS | ADDR_DATA);
    return *address;
}

void lcd_clear(unsigned char color)
{
    unsigned char mask;
    unsigned char i, j;

    mask = (color)?0xFF:0x00;
    for( i = 0; i < 4; i++ )
    {
        lcd_setpage(i);
        lcd_setcol(0, 0);
        lcd_setcol(1, 0);
        for( j = 0; j < LCD_WIDTH/2; j++)
        {
            lcd_writebyte(0, mask);
            lcd_writebyte(1, mask);
        }
    }
}

void lcd_scrolldown()
{
    volatile unsigned char data0, data1;
    unsigned char i, j;

    if(font_height == LCD_FONT_8PIX)
    {
        for(i = 1; i < 4; i++) // shift data one page upper (p1->p0, p2->p1, p3->p2)
        {
            for(j = 0; j < LCD_WIDTH/2; j++)
            {
                lcd_setpage(i);
                lcd_setcol(0, j);
                lcd_setcol(1, j);
                data0 = lcd_readbyte(0);
                data1 = lcd_readbyte(1);
                data0 = lcd_readbyte(0);
                data1 = lcd_readbyte(1);
                lcd_setpage(i-1);
                lcd_setcol(0, j);
                lcd_setcol(1, j);
                lcd_writebyte(0, data0);
                lcd_writebyte(1, data1);
            }
        }
        lcd_setpage(3); // clear last (lower) page
        lcd_setcol(0, 0);
        lcd_setcol(1, 0);
        for( j = 0; j < LCD_WIDTH/2; j++)
        {
            lcd_writebyte(0, 0x00);
            lcd_writebyte(1, 0x00);
        }
    }
    else if(font_height == LCD_FONT_16PIX)
    {
        for(j = 0; j < LCD_WIDTH/2; j++) // p2->p0, p3->p1
        {
            lcd_setpage(2);
            lcd_setcol(0, j);
            lcd_setcol(1, j);
            lcd_readbyte(0);
            lcd_readbyte(1);
            data0 = lcd_readbyte(0);
            data1 = lcd_readbyte(1);
            lcd_setpage(0);
            lcd_setcol(0, j);
            lcd_setcol(1, j);
            lcd_writebyte(0, data0);
            lcd_writebyte(0, data0);

            lcd_setpage(3);
            lcd_setcol(0, j);
            lcd_setcol(1, j);
            lcd_readbyte(0);
            lcd_readbyte(1);
            data0 = lcd_readbyte(0);
            data1 = lcd_readbyte(1);
            lcd_setpage(1);
            lcd_setcol(0, j);
            lcd_setcol(1, j);
            lcd_writebyte(0, data0);
            lcd_writebyte(0, data0);
        }
	lcd_setpage(2); // clear last (lower) two pages
        lcd_setcol(0, 0);
        lcd_setcol(1, 0);
        for( j = 0; j < LCD_WIDTH/2; j++)
        {
            lcd_writebyte(0, 0x00);
            lcd_writebyte(1, 0x00);
        }
	lcd_setpage(3);
        lcd_setcol(0, 0);
        lcd_setcol(1, 0);
        for( j = 0; j < LCD_WIDTH/2; j++)
        {
            lcd_writebyte(0, 0x00);
            lcd_writebyte(1, 0x00);
        }

    }
}

void lcd_init()
{
    lcd_onoff(1);
    lcd_adc(0);
    lcd_stdrive(0);
    lcd_duty(1);
    lcd_setstring(0);
    lcd_clear(0);
}
